]> git.saurik.com Git - apple/security.git/blob - OSX/Keychain Circle Notification/KNAppDelegate.m
Security-57740.51.3.tar.gz
[apple/security.git] / OSX / Keychain Circle Notification / KNAppDelegate.m
1 /*
2 * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 #import "KNAppDelegate.h"
26 #import "KDSecCircle.h"
27 #import "KDCirclePeer.h"
28 #import "NSDictionary+compactDescription.h"
29 #import <AOSUI/NSImageAdditions.h>
30 #import <AppleSystemInfo/AppleSystemInfo.h>
31 #import <Security/SecFrameworkStrings.h>
32 #import "notify.h"
33 #import <utilities/debugging.h>
34
35 #import <Accounts/Accounts.h>
36 #import <AOSAccounts/MobileMePrefsCoreAEPrivate.h>
37 #import <AOSAccounts/MobileMePrefsCore.h>
38 #import <AOSAccounts/ACAccountStore+iCloudAccount.h>
39 #import <AOSAccounts/iCloudAccount.h>
40
41 #include <msgtracer_client.h>
42 #include <msgtracer_keys.h>
43 #include <CrashReporterSupport/CrashReporterSupportPrivate.h>
44 #import <ProtectedCloudStorage/CloudIdentity.h>
45 #import "CoreCDP/CDPFollowUpController.h"
46 #import "CoreCDP/CDPFollowUpContext.h"
47
48 static const char * const kLaunchLaterXPCName = "com.apple.security.Keychain-Circle-Notification-TICK";
49 static const NSString * const kKickedOutKey = @"KickedOut";
50 static const NSString * const kValidOnlyOutOfCircleKey = @"ValidOnlyOutOfCircle";
51 static const NSString * const kPasswordChangedOrTrustedDeviceChanged = @"TDorPasswordChanged";
52 static NSString *prefpane = @"/System/Library/PreferencePanes/iCloudPref.prefPane";
53 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
54 static NSString *KeychainPCDetailsAEAction = @"AKPCDetailsAEAction";
55
56 @implementation KNAppDelegate
57
58 static NSUserNotificationCenter *appropriateNotificationCenter()
59 {
60 return [NSUserNotificationCenter _centerForIdentifier: @"com.apple.security.keychain-circle-notification"
61 type: _NSUserNotificationCenterTypeSystem];
62 }
63
64
65 -(void) startFollowupKitRepair
66 {
67 NSError *localError = NULL;
68 CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init];
69 CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair];
70 [cdpd postFollowUpWithContext:context error:&localError ];
71 if(localError){
72 secnotice("kcn", "request to CoreCDP to follow up failed: %@", localError);
73 }
74 else
75 secnotice("kcn", "CoreCDP handling follow up");
76 }
77
78 - (void) handleDismissedNotification
79 {
80 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
81 ACAccount *primaryiCloudAccount = nil;
82
83 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
84 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
85 }
86
87 if(primaryiCloudAccount){
88 bool localICDP = false;
89 NSString *dsid = primaryiCloudAccount.icaPersonID;
90 if (dsid) {
91 NSDictionary *options = @{ (__bridge id) kPCSSetupDSID : dsid, };
92 PCSIdentitySetRef identity = PCSIdentitySetCreate((__bridge CFDictionaryRef) options, NULL, NULL);
93
94 if (identity) {
95 localICDP = PCSIdentitySetIsICDP(identity, NULL);
96 CFRelease(identity);
97 }
98 }
99 if(localICDP){
100 secnotice("kcn", "handling dismissed notification, would start a follow up");
101 [self startFollowupKitRepair];
102 }
103 }
104 else
105 secerror("unable to find primary account");
106
107 }
108
109 - (void) notifyiCloudPreferencesAbout: (NSString *) eventName
110 {
111 if (eventName == nil)
112 return;
113
114 secnotice("kcn", "notifyiCloudPreferencesAbout %@", eventName);
115
116 NSString *accountID = (__bridge_transfer NSString*)(MMCopyLoggedInAccountFromAccounts());
117 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
118 ACAccount *primaryiCloudAccount = nil;
119
120 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
121 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
122 }
123
124 if(primaryiCloudAccount){
125 AEDesc aeDesc;
126 BOOL createdAEDesc = createAEDescWithAEActionAndAccountID((__bridge NSString *) kMMServiceIDKeychainSync, eventName, accountID, &aeDesc);
127 if (createdAEDesc) {
128
129 NSArray *prefPaneURL = [NSArray arrayWithObject: [NSURL fileURLWithPath: prefpane ]];
130
131 LSLaunchURLSpec lsSpec = {
132 .appURL = NULL,
133 .itemURLs = (__bridge CFArrayRef)prefPaneURL,
134 .passThruParams = &aeDesc,
135 .launchFlags = kLSLaunchDefaults | kLSLaunchAsync,
136 .asyncRefCon = NULL,
137 };
138
139 OSErr err = LSOpenFromURLSpec(&lsSpec, NULL);
140
141 if (err)
142 secerror("Can't send event %@, err=%d", eventName, err);
143 AEDisposeDesc(&aeDesc);
144 } else {
145 secerror("unable to create and send aedesc for account: '%@' and action: '%@'\n", primaryiCloudAccount, eventName);
146 }
147 }
148 else
149 secerror("unable to find primary account");
150 }
151
152 - (void) timerCheck
153 {
154 NSDate *nowish = [NSDate new];
155
156 self.state = [KNPersistentState loadFromStorage];
157 if ([nowish compare:self.state.pendingApplicationReminder] != NSOrderedAscending) {
158 secnotice("kcn", "REMINDER TIME: %@ >>> %@", nowish, self.state.pendingApplicationReminder);
159
160 // self.circle.rawStatus might not be valid yet
161 if (SOSCCThisDeviceIsInCircle(NULL) == kSOSCCRequestPending) {
162 // Still have a request pending, send reminder, and also in addtion to the UI
163 // we need to send a notification for iCloud pref pane to pick up
164 CFNotificationCenterPostNotificationWithOptions(
165 CFNotificationCenterGetDistributedCenter(),
166 CFSTR("com.apple.security.secureobjectsync.pendingApplicationReminder"),
167 (__bridge const void *) [self.state.applicationDate description], NULL, 0
168 );
169
170 [self postApplicationReminder];
171 self.state.pendingApplicationReminder = [nowish dateByAddingTimeInterval:[self getPendingApplicationReminderInterval]];
172 [self.state writeToStorage];
173 }
174 }
175 }
176
177
178 - (void) scheduleActivityAt: (NSDate *) time
179 {
180 if ([time compare:[NSDate distantFuture]] != NSOrderedSame) {
181 NSTimeInterval howSoon = [time timeIntervalSinceNow];
182 if (howSoon > 0)
183 [self scheduleActivityIn:ceil(howSoon)];
184 else
185 [self timerCheck];
186 }
187 }
188
189
190 - (void) scheduleActivityIn: (int) alertInterval
191 {
192 xpc_object_t options = xpc_dictionary_create(NULL, NULL, 0);
193 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_DELAY, alertInterval);
194 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_GRACE_PERIOD, XPC_ACTIVITY_INTERVAL_1_MIN);
195 xpc_dictionary_set_bool (options, XPC_ACTIVITY_REPEATING, false);
196 xpc_dictionary_set_bool (options, XPC_ACTIVITY_ALLOW_BATTERY, true);
197 xpc_dictionary_set_string(options, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY);
198
199 xpc_activity_register(kLaunchLaterXPCName, options, ^(xpc_activity_t activity) {
200 [self timerCheck];
201 });
202 }
203
204
205 - (NSTimeInterval) getPendingApplicationReminderInterval
206 {
207 if (self.state.pendingApplicationReminderInterval)
208 return [self.state.pendingApplicationReminderInterval doubleValue];
209 else
210 return 24*60*60;
211 }
212
213
214 // Copied from sysdiagnose/src/utils.m
215 static bool isAppleInternal(void)
216 {
217 static bool ret = false;
218 static dispatch_once_t onceToken;
219 dispatch_once(&onceToken, ^{
220 #if TARGET_OS_IPHONE
221 ret = CRIsAppleInternal();
222 #else
223 ret = CRHasBeenAppleInternalRecently();
224 #endif
225 });
226 return ret;
227 }
228
229
230 #define ICKC_EVENT_DISABLED "com.apple.security.secureobjectsync.disabled"
231 #define ICKC_EVENT_DEPARTURE_REASON "com.apple.security.secureobjectsync.departurereason"
232 #define ICKC_EVENT_NUM_PEERS "com.apple.security.secureobjectsync.numcircledevices"
233
234 - (void) applicationDidFinishLaunching: (NSNotification *) aNotification
235 {
236 appropriateNotificationCenter().delegate = self;
237 int out_taken;
238 secnotice("kcn", "Posted at launch: %@", appropriateNotificationCenter().deliveredNotifications);
239
240 //register for public key not available notification, if occurs KCN can react
241 notify_register_dispatch(kPublicKeyNotAvailable, &out_taken, dispatch_get_main_queue(), ^(int token) {
242 CFErrorRef err = NULL;
243 KNAppDelegate *me = self;
244 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(&err);
245 SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(&err);
246 me.state = [KNPersistentState loadFromStorage];
247
248 secnotice("kcn", "got public key not available notification, but won't send notification unless circle transition matches");
249 secnotice("kcn", "current circle status: %d, current departure reason: %d, last circle status: %d", currentCircleStatus, departureReason, me.state.lastCircleStatus);
250
251 if(currentCircleStatus == kSOSCCError && me.state.lastCircleStatus == kSOSCCInCircle && (departureReason == kSOSNeverLeftCircle)) {
252 secnotice("kcn", "circle status went from in circle to not in circle");
253 [self postRequirePassword];
254 }
255 me.state.lastCircleStatus = currentCircleStatus;
256
257 [me.state writeToStorage];
258 });
259
260 self.viewedIds = [NSMutableSet new];
261 self.circle = [KDSecCircle new];
262 KNAppDelegate *me = self;
263
264 [self.circle addChangeCallback:^{
265 secnotice("kcn", "{ChangeCallback}");
266
267 CFErrorRef err = NULL;
268
269 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(&err);
270
271 NSDate *nowish = [NSDate date];
272 SOSCCStatus circleStatus = SOSCCThisDeviceIsInCircle(&err);
273 me.state = [KNPersistentState loadFromStorage];
274 secnotice("kcn", "applicationDidFinishLaunching");
275
276 if(circleStatus == kSOSCCError && me.state.lastCircleStatus == kSOSCCInCircle && (departureReason == kSOSNeverLeftCircle)) {
277 CFErrorRef error = NULL;
278 SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(&error);
279 CFIndex errorCode = CFErrorGetCode(error);
280
281 if(errorCode == kSOSErrorPublicKeyAbsent){
282 secnotice("kcn", "We need the password to re-validate ourselves - it's changed on another device");
283 me.state.lastCircleStatus = currentCircleStatus;
284 [me.state writeToStorage];
285 [me postRequirePassword];
286 }
287 }
288
289 // Pending application reminder
290 secnotice("kcn", "{ChangeCallback} scheduleActivity %@", me.state.pendingApplicationReminder);
291 if (circleStatus == kSOSCCRequestPending)
292 [me scheduleActivityAt:me.state.pendingApplicationReminder];
293
294
295 // No longer in circle?
296 if ((me.state.lastCircleStatus == kSOSCCInCircle && (circleStatus == kSOSCCNotInCircle || circleStatus == kSOSCCCircleAbsent)) ||
297 (me.state.lastCircleStatus == kSOSCCCircleAbsent && circleStatus == kSOSCCNotInCircle && me.state.absentCircleWithNoReason) ||
298 me.state.debugLeftReason) {
299 enum DepartureReason reason = kSOSNeverLeftCircle;
300 if (me.state.debugLeftReason) {
301 reason = [me.state.debugLeftReason intValue];
302 me.state.debugLeftReason = nil;
303 [me.state writeToStorage];
304 } else {
305 reason = SOSCCGetLastDepartureReason(&err);
306 if (reason == kSOSDepartureReasonError) {
307 secnotice("kcn", "SOSCCGetLastDepartureReason err: %@", err);
308 }
309 if (err) CFRelease(err);
310 }
311
312 if (reason != kSOSDepartureReasonError) {
313 // Post kick-out alert
314
315 // <rdar://problem/20862435> MessageTracer data to find out how many users were dropped & reset
316 msgtracer_domain_t domain = msgtracer_domain_new(ICKC_EVENT_DISABLED);
317 msgtracer_msg_t mt_msg = NULL;
318
319 if (domain != NULL)
320 mt_msg = msgtracer_msg_new(domain);
321
322 if (mt_msg) {
323 char s[16];
324
325 msgtracer_set(mt_msg, kMsgTracerKeySignature, ICKC_EVENT_DEPARTURE_REASON);
326 snprintf(s, sizeof(s), "%u", reason);
327 msgtracer_set(mt_msg, kMsgTracerKeyValue, s);
328
329 int64_t num_peers = 0;
330 CFArrayRef peerList = SOSCCCopyPeerPeerInfo(NULL);
331 if (peerList) {
332 num_peers = CFArrayGetCount(peerList);
333 if (num_peers > 99) {
334 // Round down # peers to 2 significant digits
335 int factor;
336 for (factor = 10; num_peers >= 100*factor; factor *= 10) ;
337 num_peers = (num_peers / factor) * factor;
338 }
339 CFRelease(peerList);
340 }
341 msgtracer_set(mt_msg, kMsgTracerKeySignature2, ICKC_EVENT_NUM_PEERS);
342 snprintf(s, sizeof(s), "%lld", num_peers);
343 msgtracer_set(mt_msg, kMsgTracerKeyValue2, s);
344
345 msgtracer_set(mt_msg, kMsgTracerKeySummarize, "NO");
346 msgtracer_log(mt_msg, ASL_LEVEL_DEBUG, "");
347 }
348
349 // FIXME:
350 // 1. Write here due to [me timerCheck] => [KNPersistentState loadFromStorage] below?!?
351 // 2. Or change call order of timerCheck, pendingApplication reminder below???
352 me.state.absentCircleWithNoReason = (circleStatus == kSOSCCCircleAbsent && reason == kSOSNeverLeftCircle);
353 [me.state writeToStorage];
354 secnotice("kcn", "{ChangeCallback} departure reason %d", reason);
355
356 switch (reason) {
357 case kSOSDiscoveredRetirement:
358 case kSOSLostPrivateKey:
359 case kSOSWithdrewMembership:
360 case kSOSNeverAppliedToCircle:
361 break;
362
363 case kSOSNeverLeftCircle:
364 case kSOSMembershipRevoked:
365 case kSOSLeftUntrustedCircle:
366 default:
367 [me postKickedOutAlert: reason];
368 break;
369 }
370 }
371 }
372
373
374 // Circle applications: pending request(s) started / completed
375 if (me.circle.rawStatus != me.state.lastCircleStatus) {
376 SOSCCStatus lastCircleStatus = me.state.lastCircleStatus;
377 me.state.lastCircleStatus = circleStatus;
378
379 if (lastCircleStatus != kSOSCCRequestPending && circleStatus == kSOSCCRequestPending) {
380 secnotice("kcn", "{ChangeCallback} Pending request START");
381 me.state.applicationDate = nowish;
382 me.state.pendingApplicationReminder = [me.state.applicationDate dateByAddingTimeInterval:[me getPendingApplicationReminderInterval]];
383 [me.state writeToStorage]; // FIXME: move below? might be needed for scheduleActivityAt...
384 [me scheduleActivityAt:me.state.pendingApplicationReminder];
385 }
386
387 if (lastCircleStatus == kSOSCCRequestPending && circleStatus != kSOSCCRequestPending) {
388 secnotice("kcn", "Pending request completed");
389 me.state.applicationDate = [NSDate distantPast];
390 me.state.pendingApplicationReminder = [NSDate distantFuture];
391 [me.state writeToStorage];
392
393 // Remove reminders
394 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
395 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
396 if (note.userInfo[(NSString*) kValidOnlyOutOfCircleKey] && note.userInfo[@"ApplicationReminder"]) {
397 secnotice("kcn", "{ChangeCallback} Removing notification %@", note);
398 [appropriateNotificationCenter() removeDeliveredNotification: note];
399 }
400 }
401 }
402 }
403
404
405 // Clear out (old) reset notifications
406 if (me.circle.isInCircle) {
407 secnotice("kcn", "{ChangeCallback} me.circle.isInCircle");
408 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
409 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
410 if (note.userInfo[(NSString*) kValidOnlyOutOfCircleKey]) {
411 secnotice("kcn", "Removing existing notification (%@) now that we are in circle", note);
412 [appropriateNotificationCenter() removeDeliveredNotification: note];
413 }
414 }
415 }
416
417 //Clear out (old) password changed notifications
418 if(me.circle.isInCircle){
419 secnotice("kcn", "{ChangeCallback} me.circle.isInCircle");
420 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
421 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
422 if (note.userInfo[(NSString*) kPasswordChangedOrTrustedDeviceChanged]) {
423 secnotice("kcn", "Removing existing notification (%@) now that we are valid again", note);
424 [appropriateNotificationCenter() removeDeliveredNotification: note];
425 }
426 }
427
428 }
429
430 // Applicants
431 secnotice("kcn", "{ChangeCallback} Applicants");
432 NSMutableSet *applicantIds = [NSMutableSet new];
433 for (KDCirclePeer *applicant in me.circle.applicants) {
434 if (!me.circle.isInCircle) {
435 // Don't yammer on about circles we aren't in, and don't announce our own
436 // join requests as if the user could approve them locally!
437 break;
438 }
439 [me postForApplicant:applicant];
440 [applicantIds addObject:applicant.idString];
441 }
442
443
444 // Update notifications
445 NSUserNotificationCenter *notificationCenter = appropriateNotificationCenter();
446 secnotice("kcn", "Checking validity of %lu notes", (unsigned long)notificationCenter.deliveredNotifications.count);
447 for (NSUserNotification *note in notificationCenter.deliveredNotifications) {
448 if (note.userInfo[@"applicantId"] && ![applicantIds containsObject:note.userInfo[@"applicantId"]]) {
449 secnotice("kcn", "No longer an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
450 [notificationCenter removeDeliveredNotification:note];
451 } else {
452 secnotice("kcn", "Still an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
453 }
454 }
455
456 me.state.lastCircleStatus = circleStatus;
457
458 [me.state writeToStorage];
459 }];
460 }
461
462
463 - (BOOL) userNotificationCenter: (NSUserNotificationCenter *) center
464 shouldPresentNotification: (NSUserNotification *) notification
465 {
466 return YES;
467 }
468
469
470 - (void) userNotificationCenter: (NSUserNotificationCenter *) center
471 didActivateNotification: (NSUserNotification *) notification
472 {
473 if (notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
474 [self notifyiCloudPreferencesAbout:notification.userInfo[@"Activate"]];
475 }
476 }
477
478
479 - (void) userNotificationCenter: (NSUserNotificationCenter *) center
480 didDismissAlert: (NSUserNotification *) notification
481 {
482 [self handleDismissedNotification];
483
484 // If we don't do anything here & another notification comes in we
485 // will repost the alert, which will be dumb.
486 id applicantId = notification.userInfo[@"applicantId"];
487 if (applicantId != nil) {
488 [self.viewedIds addObject:applicantId];
489 }
490 }
491
492
493 - (void) postForApplicant: (KDCirclePeer *) applicant
494 {
495 static int postCount = 0;
496
497 if ([self.viewedIds containsObject:applicant.idString]) {
498 secnotice("kcn", "Already viewed %@, skipping", applicant);
499 return;
500 }
501
502 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
503 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
504 if ([applicant.idString isEqualToString:note.userInfo[@"applicantId"]]) {
505 if (note.isPresented) {
506 secnotice("kcn", "Already posted&presented: %@ (I=%@)", note, note.userInfo);
507 return;
508 } else {
509 secnotice("kcn", "Already posted, but not presented: %@ (I=%@)", note, note.userInfo);
510 }
511 }
512 }
513
514 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
515 // Contrary to HI spec (and I think it makes more sense)
516 // 1. otherButton == top : Not Now
517 // 2. actionButton == bottom: Continue
518 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
519 NSUserNotification *note = [NSUserNotification new];
520 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVAL_TITLE_OSX);
521 note.informativeText = [NSString stringWithFormat: (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX), applicant.name];
522 note._displayStyle = _NSUserNotificationDisplayStyleAlert;
523 note._identityImage = [NSImage bundleImage];
524 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
525 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
526 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
527 note.identifier = [[NSUUID new] UUIDString];
528 note.userInfo = @{
529 @"applicantName": applicant.name,
530 @"applicantId" : applicant.idString,
531 @"Activate" : (__bridge NSString *) kMMPropertyKeychainAADetailsAEAction,
532 };
533
534 secnotice("kcn", "About to post #%d/%lu (%@): %@", postCount, noteCenter.deliveredNotifications.count, applicant.idString, note);
535 [appropriateNotificationCenter() deliverNotification:note];
536 postCount++;
537 }
538
539 - (void) postRequirePassword
540 {
541 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
542 ACAccount *primaryiCloudAccount = nil;
543 bool localICDP = false;
544
545 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
546 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
547 }
548
549 if(primaryiCloudAccount){
550 NSString *dsid = primaryiCloudAccount.icaPersonID;
551
552 if (dsid) {
553 NSDictionary *options = @{ (__bridge id) kPCSSetupDSID : dsid, };
554 PCSIdentitySetRef identity = PCSIdentitySetCreate((__bridge CFDictionaryRef) options, NULL, NULL);
555
556 if (identity) {
557 localICDP = PCSIdentitySetIsICDP(identity, NULL);
558 CFRelease(identity);
559 }
560 }
561 if(!localICDP){
562 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
563 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
564 if (note.userInfo[(NSString*) kPasswordChangedOrTrustedDeviceChanged]) {
565 if (note.isPresented) {
566 secnotice("kcn", "Already posted & presented: %@", note);
567 [appropriateNotificationCenter() removeDeliveredNotification: note];
568 } else {
569 secnotice("kcn", "Already posted, but not presented: %@", note);
570 }
571 }
572 }
573
574 NSString *message = CFBridgingRelease(SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_OSX));
575 if (isAppleInternal()) {
576 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL), @"Device became untrusted or password changed"];
577 message = [message stringByAppendingString: reason_str];
578 }
579
580 NSUserNotification *note = [NSUserNotification new];
581 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE);
582 note.informativeText = message;
583 note._identityImage = [NSImage bundleImage];
584 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
585 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
586 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
587 note.identifier = [[NSUUID new] UUIDString];
588
589 note.userInfo = @{
590 kPasswordChangedOrTrustedDeviceChanged : @1,
591 @"Activate" : (__bridge NSString *) kMMPropertyKeychainPCDetailsAEAction,
592 };
593
594 secnotice("kcn", "body=%@", note.informativeText);
595 secnotice("kcn", "About to post #-/%lu (PASSWORD/TRUSTED DEVICE): %@", noteCenter.deliveredNotifications.count, note);
596 [appropriateNotificationCenter() deliverNotification:note];
597 }
598 else{
599 secnotice("kcn","would have posted needs password and then followed up");
600 [self startFollowupKitRepair];
601 }
602 }
603 }
604
605 - (void) postKickedOutAlert: (int) reason
606 {
607 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
608 ACAccount *primaryiCloudAccount = nil;
609 bool localICDP = false;
610
611 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
612 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
613 }
614
615 if(primaryiCloudAccount){
616 NSString *dsid = primaryiCloudAccount.icaPersonID;
617
618 if (dsid) {
619 NSDictionary *options = @{ (__bridge id) kPCSSetupDSID : dsid, };
620 PCSIdentitySetRef identity = PCSIdentitySetCreate((__bridge CFDictionaryRef) options, NULL, NULL);
621
622 if (identity) {
623 localICDP = PCSIdentitySetIsICDP(identity, NULL);
624 CFRelease(identity);
625 }
626 }
627 if(!localICDP){
628 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
629 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
630 if (note.userInfo[(NSString*) kKickedOutKey]) {
631 if (note.isPresented) {
632 secnotice("kcn", "Already posted&presented (removing): %@", note);
633 [appropriateNotificationCenter() removeDeliveredNotification: note];
634 } else {
635 secnotice("kcn", "Already posted, but not presented: %@", note);
636 }
637 }
638 }
639
640 NSString *message = CFBridgingRelease(SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_OSX));
641 if (isAppleInternal()) {
642 static const char *departureReasonStrings[] = {
643 "kSOSDepartureReasonError",
644 "kSOSNeverLeftCircle",
645 "kSOSWithdrewMembership",
646 "kSOSMembershipRevoked",
647 "kSOSLeftUntrustedCircle",
648 "kSOSNeverAppliedToCircle",
649 "kSOSDiscoveredRetirement",
650 "kSOSLostPrivateKey",
651 "unknown reason"
652 };
653 int idx = (kSOSDepartureReasonError <= reason && reason <= kSOSLostPrivateKey) ? reason : (kSOSLostPrivateKey + 1);
654 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL), departureReasonStrings[idx]];
655 message = [message stringByAppendingString: reason_str];
656 }
657
658 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
659 // Contrary to HI spec (and I think it makes more sense)
660 // 1. otherButton == top : Not Now
661 // 2. actionButton == bottom: Continue
662 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
663 NSUserNotification *note = [NSUserNotification new];
664 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE);
665 note.informativeText = message;
666 note._identityImage = [NSImage bundleImage];
667 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
668 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
669 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
670 note.identifier = [[NSUUID new] UUIDString];
671
672 note.userInfo = @{
673 kKickedOutKey : @1,
674 kValidOnlyOutOfCircleKey: @1,
675 @"Activate" : (__bridge NSString *) kMMPropertyKeychainMRDetailsAEAction,
676 };
677
678 secnotice("kcn", "body=%@", note.informativeText);
679 secnotice("kcn", "About to post #-/%lu (KICKOUT): %@", noteCenter.deliveredNotifications.count, note);
680 [appropriateNotificationCenter() deliverNotification:note];
681 }
682
683 else{
684 secnotice("kcn","postKickedOutAlert starting followup repair");
685 [self startFollowupKitRepair];
686 }
687 }
688 }
689
690 - (void) postApplicationReminder
691 {
692 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
693 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
694 if (note.userInfo[@"ApplicationReminder"]) {
695 if (note.isPresented) {
696 secnotice("kcn", "Already posted&presented (removing): %@", note);
697 [appropriateNotificationCenter() removeDeliveredNotification: note];
698 } else {
699 secnotice("kcn", "Already posted, but not presented: %@", note);
700 }
701 }
702 }
703
704 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
705 // Contrary to HI spec (and I think it makes more sense)
706 // 1. otherButton == top : Not Now
707 // 2. actionButton == bottom: Continue
708 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
709 NSUserNotification *note = [NSUserNotification new];
710 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_REMINDER_TITLE_OSX);
711 note.informativeText = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_REMINDER_BODY_OSX);
712 note._identityImage = [NSImage bundleImage];
713 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
714 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
715 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
716 note.identifier = [[NSUUID new] UUIDString];
717
718 note.userInfo = @{
719 @"ApplicationReminder" : @1,
720 kValidOnlyOutOfCircleKey: @1,
721 @"Activate" : (__bridge NSString *) kMMPropertyKeychainWADetailsAEAction,
722 };
723
724 secnotice("kcn", "About to post #-/%lu (REMINDER): %@ (I=%@)", noteCenter.deliveredNotifications.count, note, [note.userInfo compactDescription]);
725 [appropriateNotificationCenter() deliverNotification:note];
726 }
727
728 @end